package com.yy.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.yy.mapper.LockMapper;
import com.yy.mapper.StockMapper;
import com.yy.pojo.Stock;
import com.yy.utils.DistributeLockFactory;
import com.yy.utils.MysqlDistributeLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Service

public class MysqlStockService {
    @Autowired
    StockMapper stockMapper;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    LockMapper lockMapper;

    /**
     * 基于mysql实现的分布式锁
     * 利用主键唯一
     */
    public void deduct() {
        MysqlDistributeLock mysqlLock = new DistributeLockFactory().getMysqlLock(lockMapper, "lock");

        mysqlLock.lock();

        try {
            String stock = redisTemplate.opsForValue().get("stock");
            if (null != stock && stock.length() > 0) {
                int count = Integer.parseInt(stock);
                if (count > 0) {
                    redisTemplate.opsForValue().set("stock", String.valueOf(--count));
                }
            }
        } finally {
            mysqlLock.unlock();
        }
    }

    /**
     * 使用mysql乐观锁,通过version字段
     * 适用于读多写少的场景
     * 问题:
     * 1. 高并发情况下,性能极地
     * 2. ABA问题
     * 3. 读写分离情况下导致乐观锁不可靠
     */
    public void deduct4() {
        List<Stock> stocks = stockMapper.selectList(new QueryWrapper<Stock>().eq("product_code", "1001"));
        Stock stock = stocks.get(0);
        if (null != stock && stock.getCount() > 0) {
            Integer version = stock.getVersion();
            stock.setVersion(version + 1);
            stock.setCount(stock.getCount() - 1);

            int updatedRows = stockMapper.update(stock,
                    new UpdateWrapper<Stock>()
                            .eq("id", stock.getId())
                            .eq("version", version)
            );

            // 如果更新失败,折进行重试
            if (updatedRows <= 0) {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.deduct4();
            }
        }
    }

    /**
     * 使用mysql悲观锁 for update 配合事务注解
     * 适用于写多读少的场景
     * 问题:
     * 1. 性能问题
     * 2. 死锁问题,对多条数据加锁为顺序要一致
     */
    @Transactional
    public void deduct3() {
        List<Stock> stocks = stockMapper.queryStock("1001");
        Stock stock = stocks.get(0);

        if (null != stock && stock.getCount() > 0) {
            stock.setCount(stock.getCount() - 1);
            stockMapper.updateById(stock);
        }
    }

    /**
     * 使用一条sql处理,性能非常高,适用于业务简单场景
     * update tb_stock set count=count-1 where product_code='1001' and count>=1
     * 问题:
     * 1. 锁范围
     * 1) 锁的查询或者更新条件必须是索引字段
     * 2) 查询或者更新条件必须是具体的值
     * 2. 同一个商品有多条库存记录
     * 3. 无法记录库存变化前后的状态
     */
    public void deduct2() {
        stockMapper.updateStock("1001", 1);
    }

    private ReentrantLock lock = new ReentrantLock();

    /**
     * 当资源在mysql的时候,使用jvm锁可能存在的问题
     * 1. 多例模式会造成锁失效,因为每个对象对应一把锁
     *
     * @Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
     * 2. 事务注解会导致锁失效,因为在事务还未提交锁就已经释放,那么下一个事务就会读到还未提交的数据
     * @Transactional 3. 集群模式导致锁失效
     * 通过nginx实现
     */
    public void deduct1() {
        lock.lock();
        try {
            Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("product_code", "1001"));
            if (null != stock && stock.getCount() > 0) {
                stock.setCount(stock.getCount() - 1);

                stockMapper.updateById(stock);
            }
        } finally {
            lock.unlock();
        }
    }
}
